﻿// Project: Salient
// http://salient.codeplex.com
// 
// Copyright 2010, Sky Sanders <sky at skysanders.net>
// Dual licensed under the MIT or GPL Version 2 licenses.
// http://salient.codeplex.com/license
// 
// Date: April 16 2010 

#region

using System;
using System.Collections.Generic;
using System.Reflection;
using System.Runtime.Serialization;
using System.Web;
using System.Web.Script.Serialization;

#endregion

namespace Salient.Web
{
    ///<summary>
    /// Simple JSON service implementation.
    /// 
    /// Limitations: method parameters are limited to scalar types
    ///</summary>
    ///<typeparam name="TService"></typeparam>
    ///<typeparam name="TClass"></typeparam>
    public abstract class JsonHandler<TService, TClass> : JsonHandler, IHttpHandler where TClass : new()
    {
        private readonly TClass _instance = new TClass();

        /// <summary>
        /// 
        /// </summary>
        /// <param name="context"></param>
        public void ProcessRequest(HttpContext context)
        {
            ProcessRequest(context, _instance, typeof(TService));
        }

        /// <summary>
        /// 
        /// </summary>
        public virtual bool IsReusable
        {
            get { return false; }
        }
    }

    ///<summary>
    /// Simple JSON service implementation.
    /// 
    /// Limitations: method parameters are limited to scalar types
    ///</summary>
    ///<typeparam name="TService"></typeparam>
    public abstract class JsonHandler<TService> : JsonHandler, IHttpHandler
    {
        ///<summary>
        ///</summary>
        ///<param name="context"></param>
        public virtual void ProcessRequest(HttpContext context)
        {
            ProcessRequest(context, this, typeof(TService));
        }

        ///<summary>
        ///</summary>
        public virtual bool IsReusable
        {
            get { return false; }
        }
    }

    ///<summary>
    /// Simple JSON service implementation.
    /// 
    /// Limitations: method parameters are limited to scalar types
    ///</summary>
    public abstract class JsonHandler
    {

        /// <summary>
        /// 
        /// </summary>
        /// <param name="context"></param>
        /// <param name="handlerInstance"></param>
        protected virtual void ProcessRequest(HttpContext context, object handlerInstance)
        {
            ProcessRequest(context, handlerInstance, handlerInstance.GetType());
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="context"></param>
        /// <param name="handlerInstance"></param>
        /// <param name="handlerType"></param>
        protected virtual void ProcessRequest(HttpContext context, object handlerInstance, Type handlerType)
        {
            InvokeMethod(context, handlerInstance, handlerType);
        }


        /// <summary>
        /// 
        /// </summary>
        /// <param name="url"></param>
        /// <returns></returns>
        protected virtual string GetMethod(Uri url)
        {
            return url.Segments[url.Segments.Length - 1];
        }


        /// <summary>
        /// 
        /// </summary>
        /// <param name="context"></param>
        /// <param name="parameterName"></param>
        /// <returns></returns>
        protected virtual string GetParameter(HttpContext context, string parameterName)
        {

            return context.Request[parameterName];
        }


        private void InvokeMethod(HttpContext context, object handlerInstance, Type serviceType)
        {
            Uri url = context.Request.Url;

            string method = GetMethod(url);

            MethodInfo methodInfo = serviceType.GetMethod(method);

            if (methodInfo == null)
            {
                throw new InvalidOperationException(
                    string.Format("Method '{0}.{1}' not found on type '{2}'",
                    serviceType.FullName, method, handlerInstance.GetType().FullName));
            }

            JavaScriptSerializer jser = new JavaScriptSerializer();

            List<object> parameters = new List<object>();

            foreach (ParameterInfo p in methodInfo.GetParameters())
            {

                string parameterName = p.Name;
                Type parameterType = p.ParameterType;
                object pValue = null;

                string serializedParameter = GetParameter(context, parameterName);
                if (!string.IsNullOrEmpty(serializedParameter))
                {
                    try
                    {
                        pValue = Convert.ChangeType(serializedParameter, parameterType);
                    }
                    catch (Exception ex)
                    {

                        throw new SerializationException(
                            string.Format("Parameter {0}:{1} could not be deserialized from\r\n{2}",
                            parameterName, parameterType.FullName, serializedParameter), ex);
                    }
                }
                parameters.Add(pValue);
            }

            object[] invokeParams = parameters.Count == 0 ? null : parameters.ToArray();


            try
            {
                object response = methodInfo.Invoke(handlerInstance, invokeParams);
                context.Response.Write(jser.Serialize(response));
            }
            catch (TargetInvocationException ex)
            {
                // we need to get the stacktrace of the InnerException
                // and stash it otherwise it will get lost 
                throw new TargetInvocationExceptionWrapper(ex);
            }


        }
    }
}